package org.gikolet.util;

//: SoftHashMap.java
import java.util.*;
import java.lang.ref.*;

public class SoftHashMap<K, V> extends AbstractMap<K, V> {

	private final Map<K, SoftReference<V>> map = new HashMap<K, SoftReference<V>>();

	private final int refCacheSize;

	// 強参照のキャッシュ
	private final LinkedList<V> refCache = new LinkedList<V>();

	// GCされたもののReferenceがここに入る
	private final ReferenceQueue<V> queue = new ReferenceQueue<V>();

	public SoftHashMap() {
		this(100);
	}

	public SoftHashMap(int refCacheSize) {
		this.refCacheSize = refCacheSize;
	}

	public V get(Object key) {
		V result = null;

		SoftReference<V> softRef = this.map.get(key);
		if (softRef != null) {
			result = softRef.get();
			if (result == null) {
				this.map.remove(key);

			} else {
				// 参照を追加
				this.refCache.addFirst(result);

				if (this.refCache.size() > this.refCacheSize) {
					// 参照を削除
					this.refCache.removeLast();
				}
			}
		}
		return result;
	}

	private static class SoftValue<K, V> extends SoftReference<V> {
		private final K key;

		private SoftValue(V k, K key, ReferenceQueue<V> q) {
			super(k, q);

			this.key = key;
		}
	}

	@SuppressWarnings("unchecked")
	private void processQueue() {
		SoftValue<K, V> sv;
		while ((sv = (SoftValue<K, V>) this.queue.poll()) != null) {
			this.map.remove(sv.key);
		}
	}

	public V put(K key, V value) {
		processQueue();

		SoftReference<V> ref = this.map.put(key, new SoftValue<K, V>(value,
				key, this.queue));
		if (ref != null) {
			return ref.get();
		}
		return null;
	}

	public V remove(Object key) {
		processQueue();

		SoftReference<V> removedValue = this.map.remove(key);
		if (removedValue != null) {
			return removedValue.get();
		}

		return null;
	}

	public void clear() {
		this.refCache.clear();

		processQueue();
	}

	public int size() {
		processQueue();

		return this.map.size();
	}

	public Set<Map.Entry<K, V>> entrySet() {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean containsKey(Object key) {
		SoftReference<V> softRef = this.map.get(key);
		if (softRef != null) {
			return !softRef.isEnqueued();
		}
		return false;
	}
}
